package cn.hn.java.summer.db.builder;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.PrefixFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import cn.hn.java.summer.annotation.Mosaic;
import cn.hn.java.summer.exception.SnException;
import cn.hn.java.summer.utils.AppUtils;
import cn.hn.java.summer.utils.ReflectUtils;
import cn.hn.java.summer.xmlsql.SqlConfigElement;
import cn.hn.java.summer.xmlsql.SqlConverter;
import cn.hn.java.summer.xmlsql.SqlElement;
import cn.hn.java.summer.xmlsql.SqlTypeElement;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.StaxDriver;

/**
 * sql构建器
 * @author sjg
 * @version 1.0.1 2013-10-22
 *
 */
public abstract class SqlBuilder {
	static final Log logger = LogFactory.getLog(SqlBuilder.class);
	
	/**
	 * 注释正则
	 */
	static final String Regexp_Comment="(//[^\\n]*)|(<!--[^\\n]*-->)\\s*\\n";
	
	/**
	 * 选择字段正则
	 */
	//static final String Regexp_SelectFields="select (.*) from";
	
	/**
	 * 去除order by.. 正则
	 */
	static final String Regexp_Orderby="order by .*";
	
	/**
	 * 命名参数名正则
	 */
	static final String Regexp_NamedParamName="(?<!:) *: *(\\w+)";
	
	/**
	 * 取空参数正则
	 */
	//static final String Regexp_NullParam="where?(?<=\\?[%\\_]{0,10}|where)[^?]+\\?[%\\_]*";
	//static final String Regexp_NullParam="where?[^?]+\\?[%\\_]*";

	static final String SQL_CONFIG_XML="sql*.xml";
	/**
	 * sql映射-query
	 */
	static final Map<String,String> SQL_MAPPING_QUERY=new HashMap<String, String>();

	/**
	 * sql映射-update
	 */
	static final Map<String,String> SQL_MAPPING_UPDATE=new HashMap<String, String>();

	/**
	 * sql映射-insert
	 */
	static final Map<String,String> SQL_MAPPING_INSERT=new HashMap<String, String>();
	
	/**
	 * sql映射-delete
	 */
	static final Map<String,String> SQL_MAPPING_DELETE=new HashMap<String, String>();
	
	/**
	 * 查询时强制使用所有参数,不自动删除参数为空的条件
	 */
	public static final ForceUseAllParams FORCE_USEALL_PARAMS=new ForceUseAllParams(){};
	
	/**
	 * 读取sql配置
	 */
	static{
		//初始化sql配置文件
		String path=scanSqlXml();

		//监控sql文件改动
		new Thread(new SqlFileWatcher(path)).start();		
	}
	
	/**
	 * 扫描sql配置文件
	 * @return
	 */
	public static String scanSqlXml(){
		try {
			//查当前程序目录中的sql配置文件目录
			String configPath=AppUtils.getAppRoot()+"/config";
			logger.info("sql config dir found:"+configPath);
			//取下面所有以sql开头以.xml结尾的配置文件
			Collection<File> sqlXmlFiles= FileUtils.listFiles(new File(configPath),new PrefixFileFilter("sql"),new SuffixFileFilter(".xml"));
			
			//读取sql配置
			for(File file:sqlXmlFiles){
				ReadSqlXml(file);
			}
			return configPath;
			
		} catch (Exception e) {
			logger.error("scan sql*.xml config file failed",e);
		}
		return null;
	}	
	
	/**
	 * 从配置文件中解析sql配置
	 * @param xmlFile
	 */
	static void ReadSqlXml(File xmlFile){
		//解析器
		XStream xs=new XStream(new StaxDriver());
		xs.registerConverter(new SqlConverter());
		xs.processAnnotations(SqlConfigElement.class);	
				
		if(xmlFile!=null){
			SqlConfigElement config =(SqlConfigElement)xs.fromXML(xmlFile);
			//读取 查询、修改、插入、删除 sql配置
			ReadSql(config.getQuery(),SQL_MAPPING_QUERY);		
			ReadSql(config.getUpdate(),SQL_MAPPING_UPDATE);
			ReadSql(config.getInsert(),SQL_MAPPING_INSERT);
			ReadSql(config.getDelete(),SQL_MAPPING_DELETE);
		}else{
			logger.error("InputStream from "+SQL_CONFIG_XML+" is null");
		}
	}
	
	/**
	 * 读取sql存储
	 * @param el
	 * @param map
	 */
	public static void ReadSql(SqlTypeElement el, Map<String,String> map){		
		List<SqlElement> list= el.getSql();
		if(list!=null){
			for(SqlElement se : list){
				map.put(se.getId(),
						lowerCaseKeyWords(//小写关键字
							clearFragment(//清理sql碎片
								processComment(//处理注释、空格
										se.getText()
								)
							)
						)
					);
			}
		}
	}
	
	/**
	 * 处理注释、空格
	 * @return
	 */
	public static String processComment(String sql){
		if(sql!=null){
			return sql.replaceAll(Regexp_Comment, " ");
		}
		
		return sql;
	}
	
	/**
	 * 清理sql碎片
	 * @return
	 */
	public static String clearFragment(String sql){
		if(sql!=null){
			//去掉2个及以上的空白字符
			return sql.replaceAll("[\\s]{2,}", " ")
						.trim();
		}
		return sql;
	}
	
	/**
	 * 小写关键字
	 * @param sql
	 * @return
	 */
	private static String lowerCaseKeyWords(String sql){
		sql=" "+sql;
		//将select insert update delete or and where转换成小写
		sql=Pattern.compile("\\sselect\\s",Pattern.CASE_INSENSITIVE).matcher(sql).replaceAll(" select ");
		sql=Pattern.compile("\\sinsert\\s",Pattern.CASE_INSENSITIVE).matcher(sql).replaceAll(" insert ");
		sql=Pattern.compile("\\supdate\\s",Pattern.CASE_INSENSITIVE).matcher(sql).replaceAll(" update ");
		sql=Pattern.compile("\\sdelete\\s",Pattern.CASE_INSENSITIVE).matcher(sql).replaceAll(" delete ");
		sql=Pattern.compile("\\sor\\s",Pattern.CASE_INSENSITIVE).matcher(sql).replaceAll(" or ");
		sql=Pattern.compile("\\sand\\s",Pattern.CASE_INSENSITIVE).matcher(sql).replaceAll(" and ");
		sql=Pattern.compile("\\swhere\\s",Pattern.CASE_INSENSITIVE).matcher(sql).replaceAll(" where ");
		sql=Pattern.compile("\\sorder by\\s",Pattern.CASE_INSENSITIVE).matcher(sql).replaceAll(" order by ");
		sql=Pattern.compile("\\sgroup by\\s",Pattern.CASE_INSENSITIVE).matcher(sql).replaceAll(" group by ");
		return sql;
	}
		
	
	/**
	 * 将sql中转义的字符恢复
	 * @param sql
	 * @return
	 */
	public static String restoreEscapeChars(String sql){
		return sql.replace("::", ":")//恢复转义传参的:
					.replace("??", "?");//恢复转义传参的?
	}

	/**
	 * 针对Sql的格式化字符串<br/> 
	 * @param sqlId
	 * @param args
	 * @param isHardCode 是否硬编码（直接拼sql）
	 * @return
	 * @throws SnException 
	 */
	public static FormatResult format(String sqlId,boolean isHardCode,Object... args){
		//取到配置的sql
		String sql=getSqlById(sqlId);
		
		//取不到，表示传入的为完整sql
		if(sql==null){
		    logger.warn("sqlId:"+sqlId+" not found!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
			return new FormatResult(sqlId,args);
		}
		
		FormatResult rst=new FormatResult(sql,args);
		
		//处理强制使用所有
		boolean isForce= processForceUseAllParams(rst);
		
		//处理硬编码参数
		processHardCode(rst);
		
		//有传参数
		if(rst.getArgs()!=null && rst.getArgs().length>0){
			
			if(!ReflectUtils.hasProperty(rst.getArgs()[0])){
				//第一个参数为无属性对象，说明是使用参数列表传参，不用处理
			}else{
				//使用对象传参,转换成列表式
				rst.setArgs(getOrderedParams(rst.getSql(),rst.getArgs()[0]));
			}
		}else{
			//无参数,设置每个参数位置的参数为null,后面删除参数时会将所有为null的参数删除
			rst.setArgs(getEmptyParams(rst.getSql(),rst.getArgs()));
		}
		
		//将命名参数替换成"?"
		sql=rst.getSql().replaceAll(Regexp_NamedParamName, "?");		
		
		//恢复转义字符
		//sql=restoreEscapeChars(sql);//不能用转义，有问题	
		
		//保存
		rst.setSql(sql);
		
		//不强制使用所有参数
		if(!isForce){
			//删除空参数
			deleteNullParam(rst);
		}

		//处理直接拼sql的情况，用存储过程分页查询时只能用拼接的方式方法
		if(isHardCode){
			hardCodeSql(rst);
		}
				
		logger.debug("format sql ["+sqlId+"]=>"+rst.getSql());
		//调试参数输出
		if(logger.isDebugEnabled() && rst.getArgs()!=null){
			StringBuilder sbArgs=new StringBuilder("params=>");
			for(int i=0; i<rst.getArgs().length; i++){
				sbArgs.append("p").
				append(i+1).
				append(":").
				append(rst.getArgs()[i]).
				append(",");
			}
			logger.info(sbArgs.toString());
		}
		return rst;
	}
	
	/**
	 * 处理强制使用所有参数
	 * @param fr
	 */
	private static boolean processForceUseAllParams(FormatResult fr){
		Object[] args= fr.getArgs();
		List<Object> newArg=new ArrayList<Object>();
		boolean isForce=false;
		for(Object arg:args){
			//保存非强制参数
			if(!(arg instanceof ForceUseAllParams)){
				newArg.add(arg);
			}else{
				isForce=true;
			}
		}
		fr.setArgs(newArg.toArray());
		return isForce;
	}
	
	/**
	 * 处理硬编码参数
	 * @param fr
	 */
	public static void processHardCode(FormatResult fr){
		Object[] args= fr.getArgs();
		List<Object> newArg=new ArrayList<Object>();
		
		for(Object obj: args){
			if(obj instanceof HardCode){
				HardCode hc=(HardCode)obj;
				//替换硬编码参数
				fr.setSql(fr.getSql().replace("#"+hc.getKey()+"#", 
							//过滤'
							hc.isEscape()? hc.getParam().replace("'", "''"):hc.getParam()
						)
				);
			}else{
				//使用注解硬编码参数(拼接sql)的
				//找出拼接的占位符
				Matcher matcher= Pattern.compile("#([^#\\s]+)#").matcher(fr.getSql());
				while(matcher.find()){
					String pos=matcher.group(1);
					//找到拼接的字段
					Field mosField=null;
					try {
						mosField = ReflectUtils.getAccessbleField(obj.getClass(),pos);
					} catch (Exception e) {
						//忽略
					}
					if(mosField==null){
						continue;
					}
					//是否有Mosaic注解
					if(mosField.getAnnotation(Mosaic.class)==null){
						continue;
					}
					//取拼接字段的值
					Object posValue=ReflectUtils.getFieldValue(obj, pos);
					if(posValue==null){
						posValue="";
					}
					//转义'
					posValue=posValue.toString().replace("'", "''");
					//替换占位符为值
					fr.setSql(fr.getSql().replaceAll("#"+pos+"#", posValue.toString()));
				}
				newArg.add(obj);
			}
		}
		fr.setArgs(newArg.toArray());
	}
	
	/**
	 * 针对Sql的格式化字符串<br/> 无参数
	 * @param sqlId
	 * @return
	 */
	public static String format(String sqlId){
		return format(sqlId,false,new Object[0]).getSql();
	}
	
	/**
	 * 硬编码sql
	 * @param rst
	 */
	public static void hardCodeSql(FormatResult rst){
		if(rst.getArgs()==null || rst.getArgs().length==0){
			return;
		}
		StringBuilder sbSql=new StringBuilder(rst.getSql());
		for(int i=0; i<rst.getArgs().length; i++){
			int idx=sbSql.indexOf("?");	
			//将"?"替换成参数值
			sbSql.replace(idx, idx+1, getArgStr(rst.getArgs()[i]));
		}
		//保存
		rst.setArgs(new Object[0]);
		rst.setSql(sbSql.toString());
	}
	
	/**
	 * 取参数连接串，sql中只有字符串才需要用单引号括起来
	 * @param obj
	 * @return
	 */
	private static String getArgStr(Object obj){
		if(obj instanceof java.lang.String){
			return "'"+obj
			.toString()
			//转义"'"
			.replace("'", "''")
			//还可以过滤其它非法字符
			+"'";
		}
		return obj.toString();
	}
	
	/**
	 * 取按照sql参数排好序的参数
	 * @param sql
	 * @param arg
	 * @param orderedArgs
	 * @return
	 */
	private static Object[] getOrderedParams(String sql, Object arg){
		if(arg==null){
			return new Object[0];
		}
		
		//排序好的参数
		List<Object> orderedArgs=new ArrayList<Object>();
		
		Pattern p=Pattern.compile(Regexp_NamedParamName);		
		Matcher mch= p.matcher(sql);		
		
		Class<?> cls=arg.getClass();		
		String fieldName="";
		
		try {
			while(mch.find()){
				fieldName=mch.group(1);
				//取出get方法
				Method getter=ReflectUtils.getGetter(cls, fieldName);
				if(getter!=null){
					//保存参数值
					Object getValue=getter.invoke(arg);
					/*
					//将yyyy-MM-dd或yyyy-MM-dd HH:mm:ss时间格式转换为yyyyMMdd或yyyyMMddHHmmss格式
					if(getValue!=null && getValue.toString().matches("^\\d{4}-\\d{2}-\\d{2}( \\d{2}:\\d{2}:\\d{2})?$")){
						getValue=getValue.toString().
										replace("-", "").
										replace(" ", "").
										replace(":", "");
					}
					*/
					
					orderedArgs.add(getValue);
				}else{
					orderedArgs.add(null);
				}
			}
		} catch (Exception e) {
			logger.error("Gets the parameters error:"+fieldName,e);
			//throw new SnException(StrUtil.format("获取参数出错:{0},{1}",fieldName,e.getMessage()),e);
		}	
		return orderedArgs.toArray();
	}
	
	/**
	 * 取空的参数列表
	 * @param sql
	 * @param arg
	 * @return
	 */
	private static Object[] getEmptyParams(String sql, Object arg){				
		//排序好的参数
		List<Object> orderedArgs=new ArrayList<Object>();
		
		Pattern p=Pattern.compile(Regexp_NamedParamName);		
		Matcher mch= p.matcher(sql);		
				
		while(mch.find()){
			//为每个参数位置设置空参数
			orderedArgs.add(null);
		}
		
		return orderedArgs.toArray();
	}
	
	/**
	 * 删除空参数
	 * @param rst
	 */
	private static void deleteNullParam(FormatResult rst){
		//对于修改、插入、删除操作不能删除为空的参数
		String startWith=rst.getSql().trim();
		if(startWith.startsWith("update") || 
				startWith.startsWith("insert") || 
				startWith.startsWith("delete")){
			return;
		}
		
		if(rst.getArgs()==null || rst.getArgs().length==0){
			return;
		}
		
		List<Object> args=new ArrayList<Object>();
		int argLen=rst.getArgs().length;
		int i=argLen-1;
		
		//匹配查询条件参数
		String sql=rst.getSql();
		StringBuilder sbSql=new StringBuilder(sql);
		
		//最后一个问号位置
		int lastIndex=-1;
		while(i>=0){
			lastIndex=sbSql.lastIndexOf("?",lastIndex==-1?sbSql.length()-lastIndex:lastIndex-1);
			//判断?前后是否有like用的字符:%、_
			if(lastIndex>=0 && lastIndex<sbSql.length()-1){
				char cAfter= sbSql.charAt(lastIndex+1);
				char cBefore=sbSql.charAt(lastIndex-1);
				//增加一个长度
				if(cAfter=='%' || cAfter=='_'){
					//加上后面的通配符
					if(rst.getArgs()[i]!=null){
						rst.getArgs()[i]=rst.getArgs()[i].toString()+cAfter;
					}
					//去掉sql中的通配符
					sbSql.replace(lastIndex+1, lastIndex+2, "");
				}
				if(cBefore=='%' || cBefore=='_'){
					//加上前面的匹配符
					if(rst.getArgs()[i]!=null){
						rst.getArgs()[i]=cBefore+rst.getArgs()[i].toString();
					}

					//去掉sql中的通配符
					sbSql.replace(lastIndex-1, lastIndex, "");
					lastIndex--;
				}
			}		
			
			boolean delArg=false;
			//参数为空
			if(rst.getArgs()[i]==null || 
					StringUtils.isBlank(rst.getArgs()[i].toString()) || 
					rst.getArgs()[i].toString().equals("%%") ||
					rst.getArgs()[i].toString().equals("%")
					){				
				//往前找and/or/where
				int startIndex=sbSql.lastIndexOf("and ", lastIndex);
				int startIndex1=sbSql.lastIndexOf("or ", lastIndex);
				int startIndex2=sbSql.lastIndexOf("where ", lastIndex);
				//找到最近一个				
				startIndex=Math.max(Math.max(startIndex, startIndex1),startIndex2);
				if(startIndex!=-1 && startIndex<lastIndex){
					int count1=sbSql.length();
					//找到，删除或替换成where 1=1
					sbSql.replace(startIndex, lastIndex+1, startIndex==startIndex2?"where 1=1":"");
					//减去减少的数量
					lastIndex-=count1-sbSql.length();
					delArg=true;
				}
			}
			
			//删除空参数
			if(!delArg){
				args.add(rst.getArgs()[i]);
			}
			i--;
		}
				
		//保存
		Object[] arr=args.toArray();
		org.apache.commons.lang3.ArrayUtils.reverse(arr);
		rst.setArgs(arr);
		rst.setSql(sbSql.toString().replaceAll(" {2,}", " "));
	}
	
	/**
	 * 组织成count语句
	 * @param sql
	 * @return
	 */
	public static String formCountSql(String sql){
		
		//生成count语句
		if(sql!=null){
			
			//去掉order by...
			sql=Pattern.compile(Regexp_Orderby,Pattern.CASE_INSENSITIVE).
			matcher(sql).replaceFirst("");
			
			//生成select count(0) from xx
			if(sql.indexOf("group by")<0){
				//无group by的sql可以去掉查询字段
				//找到第一个select与第一个from之间的字符替换
				sql="select count(0) "+sql.substring(sql.toLowerCase().indexOf("from"));				
			}else{
				//有group by的在外面包一层count
				sql="select count(0) from(#innerTable#) _inner_table".replace("#innerTable#",sql);
			}
		}
		return sql;
	}
	
	/**
	 * 取配置的sql，找不到返回原sqlId
	 * @param sqlId
	 * @return
	 */
	private static String getSqlById(String sqlId){
		String sql=null;
		//取对应sql
		if(SQL_MAPPING_QUERY.containsKey(sqlId)){
			sql=SQL_MAPPING_QUERY.get(sqlId);
		}else if(SQL_MAPPING_DELETE.containsKey(sqlId)){
			sql=SQL_MAPPING_DELETE.get(sqlId);
		}else if(SQL_MAPPING_INSERT.containsKey(sqlId)){
			sql=SQL_MAPPING_INSERT.get(sqlId);
		}else if(SQL_MAPPING_UPDATE.containsKey(sqlId)){
			sql=SQL_MAPPING_UPDATE.get(sqlId);
		}
		return sql;
	}
	
	/**
	 * 强制使用所有参数,当查询参数中包含此项时,不去掉为空参数的查询条件
	 * @author sjg
	 * @version 1.0.1 2014-3-17
	 *
	 */
	private interface ForceUseAllParams{
		
	}
}
